package com.jichangxiu.common.utils;

import cn.hutool.core.io.IoUtil;
import com.jichangxiu.common.exception.FrameworkException;

import javax.crypto.Cipher;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * RsaUtils:
 *
 * @author Jcx
 * @create 2023-04-03 21:49
 */
public class RsaUtils {

    private static final int DEFAULT_KEY_SIZE = 2048;

    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径，相对于 classpath
     * @return 公钥对象
     * @throws Exception 异常
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        return getPublicKey(readFile(filename));
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径，相对于 classpath
     * @return 私钥对象
     * @throws Exception 异常
     */
    public static PrivateKey getPrivateKey(String filename) throws NoSuchAlgorithmException, InvalidKeySpecException {
        return getPrivateKey(readFile(filename));
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return 公钥
     * @throws Exception 异常
     */
    private static PublicKey getPublicKey(byte[] bytes) throws Exception {
        return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(bytes)));
    }

    /**
     * 获取私钥
     *
     * @param bytes 私钥的字节形式
     * @return 密钥
     * @throws Exception 异常
     */
    private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(bytes)));
    }

    /**
     * 根据密文，生成 rsa 公钥和私钥,并写入指定文件
     *
     * @param secret  生成密钥的密文
     * @param keySize 大小
     */
    public static KeyPair generateRsa(String secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        return keyPairGenerator.genKeyPair();
    }

    /**
     * 根据密文，生成 rsa 公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        generateKey(publicKeyFilename, privateKeyFilename, secret, DEFAULT_KEY_SIZE);
    }

    /**
     * 根据密文，生成 rsa 公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     * @param keySize            关键字大小
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
        KeyPair keyPair = generateRsa(secret, keySize);
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    /**
     * 公钥加密
     *
     * @param text 待加密的明文字符串
     * @return 加密后的密文
     */
    public static String encrypt(String text) {
        try {
            PublicKey publicKey = RsaUtils.getPublicKey("rsa_public.pub");
            return encrypt(text, publicKey);
        } catch (Exception e) {
            throw FrameworkException.build("加密字符串[" + text + "]时遇到异常", e);
        }
    }

    /**
     * 公钥加密
     *
     * @param text      待加密的明文字符串
     * @param publicKey 公钥
     * @return 加密后的密文
     */
    public static String encrypt(String text, PublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] tempBytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(tempBytes);
        } catch (Exception e) {
            throw FrameworkException.build("加密字符串[" + text + "]时遇到异常", e);
        }
    }

    /**
     * 私钥解密
     *
     * @param secretText 待解密的密文字符串
     * @return 解密后的明文
     */
    public static String decrypt(String secretText) {
        try {
            PrivateKey privateKey = RsaUtils.getPrivateKey("private.key");
            return decrypt(secretText, privateKey);
        } catch (Exception e) {
            throw FrameworkException.build("解密字符串[" + secretText + "]时遇到异常", e);
        }
    }

    /**
     * 私钥解密
     *
     * @param secretText 待解密的密文字符串
     * @param privateKey 私钥
     * @return 解密后的明文
     */
    public static String decrypt(String secretText, PrivateKey privateKey) {
        try {
            // 生成私钥
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // 密文解码
            byte[] secretTextDecoded = Base64.getDecoder().decode(secretText.getBytes(StandardCharsets.UTF_8));
            byte[] tempBytes = cipher.doFinal(secretTextDecoded);
            return new String(tempBytes);
        } catch (Exception e) {
            throw FrameworkException.build("解密字符串[" + secretText + "]时遇到异常", e);
        }
    }

    /**
     * 根据文件路径或文件名读取文件
     * <p>
     * 如果是路径直接读取指定路径文件，如果是文件名则读取项目资源路径下的文件
     *
     * @param fileName 文件路径或文件名
     * @return 文件字节数据
     */
    private static byte[] readFile(String fileName) {
        try {
            File file = new File(fileName);
            if (file.exists()) {
                return Files.readAllBytes(file.toPath());
            } else {
                InputStream resourceAsStream = RsaUtils.class.getClassLoader().getResourceAsStream(fileName);
                assert resourceAsStream != null;
                return IoUtil.readBytes(resourceAsStream);
            }
        } catch (IOException e) {
            throw FrameworkException.build("读取文件异常", e);
        }
    }

    /**
     * 向指定路径写文件
     *
     * @param destPath 路径
     * @param bytes    文件字节数据
     */
    private static void writeFile(String destPath, byte[] bytes) {
        try {
            File dest = new File(destPath);
            if (!dest.exists()) {
                dest.createNewFile();
            }
            Files.write(dest.toPath(), bytes);
        } catch (IOException e) {
            throw FrameworkException.build("写文件异常", e);
        }
    }

}
